package com.xinghen.common.util;

import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author xinghen
 * @ClassName: MybatisGeneratorUtils
 * @Package: com.xinghen.common.util
 * @Description TODO
 * @create 2017-12-06 11:22
 */
public class MybatisGeneratorUtils {

    private MybatisGeneratorUtils() {
    }

    /**
     * 一键generate
     *
     * @param generatorConfig
     * @throws Exception
     */
    public static void generate(String generatorConfig) throws Exception {
        generate(generatorConfig, generatorConfig, "templates", null, null, null);
    }

    /**
     * 一键generate
     *
     * @param generatorConfig
     * @param tablePrefix
     * @throws Exception
     */
    public static void generate(String generatorConfig, String tablePrefix) throws Exception {
        generate(generatorConfig, generatorConfig, "templates", tablePrefix, null, null);
    }


    /**
     * 一键generate
     *
     * @param generatorConfig
     * @param tablePrefix
     * @throws Exception
     */
    public static void generate(String generatorConfig, String tablePrefix, String tablePrefixExclude) throws Exception {
        generate(generatorConfig, generatorConfig, "templates", tablePrefix, tablePrefixExclude, null);
    }

    /**
     * 一键generate
     *
     * @param generatorConfig
     * @param tablePrefix
     * @param tables2primaryKeyMap
     * @throws Exception
     */
    public static void generate(String generatorConfig, String tablePrefix, String tablePrefixExclude,
                                Map<String, String> tables2primaryKeyMap) throws Exception {
        generate(generatorConfig, generatorConfig, "templates", tablePrefix, tablePrefixExclude, tables2primaryKeyMap);
    }

    /**
     * 一键generate
     *
     * @param dbConfig
     * @param generatorConfig
     * @param tablePrefix
     * @throws Exception
     */
    public static void generate(String dbConfig, String generatorConfig, String tablePrefix, String tablePrefixExclude) throws Exception {
        generate(dbConfig, generatorConfig, "templates", tablePrefix, tablePrefixExclude, null);
    }

    /**
     * 一键generate
     *
     * @param dbConfig
     * @param generatorConfig
     * @param templateDir
     * @param tablePrefix
     * @throws IOException
     */
    public static void generate(String dbConfig, String generatorConfig, String templateDir, String tablePrefix,
                                String tablePrefixExclude, Map<String, String> tables2primaryKeyMap) throws Exception {
        generatorConfig = getRealPath(generatorConfig);
        templateDir = getRealPath(templateDir);

        //数据库配置
        Properties db = readProperties(getRealPath(dbConfig));
        String driver = db.getProperty("generator.jdbc.driver");
        String url = db.getProperty("generator.jdbc.url");
        String username = db.getProperty("generator.jdbc.username");
        String password = db.getProperty("generator.jdbc.password");

        String database = getDatabaseName(url);

        String modelPackage = null;
        String mapperXmlPackage = null;
        String mapperInterfacePackage = null;
        String servicePackage = null;
        String serviceImplPackage = null;
        String controllerPackage = null;
        String modelProject = null;
        String daoProject = null;
        String serviceProject = null;
        String serviceImplProject = null;
        String controllerProject = null;
        String needGeneratedTables = null;

        // 自己指定生成代码的位置
        if (StringUtils.isNotBlank(generatorConfig)) {
            Properties properties = readProperties(generatorConfig);
            modelPackage = properties.getProperty("generator.model.package");
            mapperXmlPackage = properties.getProperty("generator.mapper.xml.package");
            mapperInterfacePackage = properties.getProperty("generator.mapper.interface.package");
            servicePackage = properties.getProperty("generator.service.package");
            serviceImplPackage = properties.getProperty("generator.service.impl.package");
            controllerPackage = properties.getProperty("generator.controller.package");
            modelProject = properties.getProperty("generator.model.project");
            daoProject = properties.getProperty("generator.dao.project");
            serviceProject = properties.getProperty("generator.service.project");
            serviceImplProject = properties.getProperty("generator.service.impl.project");
            controllerProject = properties.getProperty("generator.controller.project");
            needGeneratedTables = properties.getProperty("generator.tables");
        }

        String generatorConfigXmlPath = new File(generatorConfig).getParent() + "/generatorConfig.xml";

        GeneratorConfig config = new GeneratorConfig(driver, url, username, password, modelProject, daoProject, serviceProject,
                serviceImplProject, controllerProject, modelPackage, mapperXmlPackage, mapperInterfacePackage,
                servicePackage, serviceImplPackage, controllerPackage, templateDir);

        List<Map<String, Object>> tables = new ArrayList<>();
        //如果用户指定generator.tables的值，则不需要生成全部表
        if (StringUtils.isBlank(needGeneratedTables)) {
            // 获取需要映射的所有数据库表
            String sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '"
                    + database + "';";
            if (StringUtils.isNotBlank(tablePrefix)) {
                sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '"
                        + database + "' AND table_name LIKE '" + tablePrefix + "_%';";
            } else if (StringUtils.isNotBlank(tablePrefixExclude)) {
                sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '"
                        + database + "' AND table_name NOT LIKE '" + tablePrefix + "_%';";
            }
            JdbcUtils jdbcUtils = new JdbcUtils(driver, url, username, password);
            List<Map<String, Object>> result = jdbcUtils.selectByParam(sql, null);
            Map<String, Object> table;
            for (Map<String, Object> map : result) {
                table = new HashMap<>(2);
                table.put("tableName", map.get("TABLE_NAME"));
                table.put("modelName", CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL,
                        map.get("TABLE_NAME").toString()));
                tables.add(table);
            }
            jdbcUtils.release();
        } else {
            String[] tablesArray = StringUtils.split(needGeneratedTables, ",");
            Map<String, Object> table;
            for (String tableName : tablesArray) {
                table = new HashMap<>(2);
                table.put("tableName", tableName);
                table.put("modelName", CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName));
                tables.add(table);
            }
        }

        // 生成generatorConfig.xml文件
        VelocityContext context = new VelocityContext();
        context.put("dbConfig", dbConfig);
        context.put("modelPackage", config.getModelPackage());
        context.put("modelProject", config.getModelProject());
        context.put("mapperXmlPackage", config.getMapperXmlPackage());
        context.put("daoProject", config.getDaoProject());
        context.put("mapperInterfacePackage", config.getMapperInterfacePackage());
        context.put("tables", tables);
        context.put("tables2primaryKeyMap", tables2primaryKeyMap);

        System.out.println("========== 开始生成generatorConfig.xml文件 ==========");
        VelocityUtils.generate(context, config.getGeneratorConfigTemplate(), generatorConfigXmlPath);
        System.out.println("========== 结束生成generatorConfig.xml文件 ==========");

        System.out.println("========== 开始运行MybatisGenerator ==========");
        List<String> warnings = new ArrayList<>();
        File xml = new File(generatorConfigXmlPath);
        ConfigurationParser parser = new ConfigurationParser(warnings);
        Configuration configuration = parser.parseConfiguration(xml);
        DefaultShellCallback callback = new DefaultShellCallback(true);
        MyBatisGenerator generator = new MyBatisGenerator(configuration, callback, warnings);
        generator.generate(null);
        System.out.println("========== 结束运行MybatisGenerator ==========");

        String ctime = new SimpleDateFormat("yyyy/MM/dd HH:mm").format(new Date());

        String servicePath = makesurePackageExist(config.getServiceProject(), config.getServicePackage());
        String serviceImplPath = makesurePackageExist(config.getServiceImplProject(), config.getServiceImplPackage());
        String controllerPath = makesurePackageExist(config.getControllerProject(), config.getControllerPackage());

        System.out.println("========== 开始生成Service、ServiceImpl及Controller ==========");
        context.put("servicePackage", config.getServicePackage());
        context.put("serviceImplPackage", config.getServiceImplPackage());
        context.put("controllerPackage", config.getControllerPackage());
        context.put("mapperInterfacePackage", config.getMapperInterfacePackage());
        context.put("ctime", ctime);
        for (Map<String, Object> table : tables) {
            String modelName = table.get("modelName").toString();
            context.put("modelName", modelName);
            context.put("model_name", StringUtils.uncapitalize(modelName));
            // 生成Service接口
            VelocityUtils.generate(context, config.getiServiceTemplate(), servicePath + "/" + modelName + "Service.java");

            // 生成Service实现
            VelocityUtils.generate(context, config.getServiceImplTemplate(), serviceImplPath + "/" + modelName + "ServiceImpl.java");

            // 生成Controller
            VelocityUtils.generate(context, config.getControllerTemplate(), controllerPath + "/" + modelName + "Controller.java");
        }
        System.out.println("========== 结束生成Service、ServiceImpl及Controller ==========");

    }

    /**
     * 确保包路径存在
     *
     * @param projctName
     * @param packageName
     * @return
     */
    private static String makesurePackageExist(String projctName, String packageName) {
        String fullPath = projctName + "/src/main/java/" + packageName.replaceAll("\\.", "/");
        File dir = new File(fullPath);
        if (dir.isDirectory()) {
            File[] files = dir.listFiles();
            for (int i = 0, len = files.length; i < len; i++) {
                files[i].delete();
            }
        } else {
            dir.mkdirs();
        }
        return fullPath;
    }

    /**
     * 读取properties配置文件
     *
     * @param path
     * @return
     * @throws IOException
     */
    private static Properties readProperties(String path) throws IOException {
        Properties properties = new Properties();
        properties.load(Files.newReader(new File(path), Charsets.UTF_8));
        return properties;
    }

    /**
     * 获取文件的绝对路径
     *
     * @param path
     * @return
     */
    private static String getRealPath(String path) {
        path = Resources.getResource(path).getPath();
        if (path.contains("/target/classes")) {
            path = path.replace("/target/classes", "/src/main/resources");
        }
        if (System.getProperty("os.name").contains("Windows")) {
            path = StringUtils.stripStart(path, "/");
        }
        return path;
    }

    /**
     * 获取数据库名称
     *
     * @param url
     * @return
     */
    private static String getDatabaseName(String url) {
        String databaseName = StringUtils.substring(url, url.lastIndexOf("/") + 1, url.length());
        if (databaseName.contains("?")) {
            databaseName = StringUtils.substringBefore(databaseName, "?");
        }
        return databaseName;
    }

    /**
     * generator配置类
     */
    private static class GeneratorConfig {

        private static final String[] TEMPLATE_KEYS = {"generatorConfig", "Service", "ServiceImpl", "Controller"};

        private String generatorConfigTemplate;
        private String iServiceTemplate;
        private String serviceImplTemplate;
        private String controllerTemplate;

        private boolean generateService;
        private boolean generateController;

        private String driverClass;
        private String url;
        private String username;
        private String password;

        private String modelPackage;
        private String mapperXmlPackage;
        private String mapperInterfacePackage;
        private String servicePackage;
        private String serviceImplPackage;
        private String controllerPackage;

        private String modelProject;
        private String daoProject;
        private String serviceProject;
        private String serviceImplProject;
        private String controllerProject;

        public GeneratorConfig(String driverClass, String url, String username, String password) {
            this(driverClass, url, username, password, null, null, null, null, null, null, null, null, null, null, null, null);
        }

        public GeneratorConfig(String driverClass, String url, String username, String password,
                               String modelProject, String daoProject, String serviceProject,
                               String serviceImplProject, String controllerProject, String modelPackage,
                               String mapperXmlPackage, String mapperInterfacePackage, String servicePackage,
                               String serviceImplPackage, String controllerPackage, String templateDir) {
            this.driverClass = driverClass;
            this.url = url;
            this.username = username;
            this.password = password;
            this.modelProject = generateProject(modelProject);
            this.daoProject = generateProject(daoProject);
            this.modelPackage = StringUtils.isBlank(modelPackage) ? "com.xinghen.test.model" : modelPackage;
            this.mapperXmlPackage = StringUtils.isBlank(mapperXmlPackage) ? "mappers" : mapperXmlPackage;
            this.mapperInterfacePackage = StringUtils.isBlank(mapperInterfacePackage)
                    ? "com.xinghen.test.mapper" : mapperInterfacePackage;

            if (StringUtils.isBlank(templateDir)) {
                templateDir = "templates";
            }
            Map<String, String> map = getTemplatePath(templateDir);
            this.generatorConfigTemplate = map.get(TEMPLATE_KEYS[0]);
            this.iServiceTemplate = map.get(TEMPLATE_KEYS[1]);
            this.serviceImplTemplate = map.get(TEMPLATE_KEYS[2]);
            this.controllerTemplate = map.get(TEMPLATE_KEYS[3]);
            if (StringUtils.isNotBlank(this.iServiceTemplate)) {
                this.generateService = true;
                this.servicePackage = StringUtils.isBlank(servicePackage)
                        ? "com.xinghen.test.service" : servicePackage;
                this.serviceImplPackage = StringUtils.isBlank(serviceImplPackage)
                        ? "com.xinghen.test.service.impl" : serviceImplPackage;
                this.serviceProject = generateProject(serviceProject);
                this.serviceImplProject = generateProject(serviceImplProject);
//                makesurePackageExist(this.serviceProject + "/src/main/java/" + this.servicePackage);
//                makesurePackageExist(this.serviceImplProject + "/src/main/resources/" + this.serviceImplPackage);
            }
            if (StringUtils.isNotBlank(this.controllerTemplate)) {
                this.generateController = true;
                this.controllerPackage = StringUtils.isBlank(controllerPackage)
                        ? "com.xinghen.test.controller" : controllerPackage;
                this.controllerProject = generateProject(controllerProject);
//                makesurePackageExist(this.controllerProject + "/src/main/java/" + this.controllerPackage);
            }

        }


        /**
         * 通过模板目录获取所有模板
         *
         * @param templateDir
         * @return
         */
        private Map<String, String> getTemplatePath(String templateDir) {
//            templateDir = generateProject(null) + "/src/main/resources/" + templateDir;
            Map<String, String> map = new HashMap<>();
            File[] files = new File(templateDir).listFiles((dir, name) -> {
                boolean flag = false;
                if (name.endsWith(".vm")) {
                    String template = name.substring(name.lastIndexOf("/") + 1, name.indexOf(".vm"));
                    for (String templateKey : TEMPLATE_KEYS) {
                        if (templateKey.equals(template)) {
                            flag = true;
                            break;
                        }
                    }
                }
                return flag;
            });
            if (files != null && files.length > 0) {
                for (File file : files) {
                    String templateName = Files.getNameWithoutExtension(file.getName());
                    map.put(templateName, file.getAbsolutePath());
                }
            }
            return map;
        }

        /**
         * 通过项目名称获取项目全路径
         *
         * @param projectName
         * @return
         */
        private String generateProject(String projectName) {
            String path;
            if (StringUtils.isBlank(projectName)) {
                path = GeneratorConfig.class.getResource("/").getPath();
                if (StringUtils.contains(path, "/target/classes/")) {
                    path = StringUtils.replace(path, "/target/classes/", "");
                }
            } else {
                path = new File(projectName).getAbsolutePath();
            }
            return path;
        }

        public String getDriverClass() {
            return driverClass;
        }

        public void setDriverClass(String driverClass) {
            this.driverClass = driverClass;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public String getModelPackage() {
            return modelPackage;
        }

        public void setModelPackage(String modelPackage) {
            this.modelPackage = modelPackage;
        }

        public String getMapperXmlPackage() {
            return mapperXmlPackage;
        }

        public void setMapperXmlPackage(String mapperXmlPackage) {
            this.mapperXmlPackage = mapperXmlPackage;
        }

        public String getMapperInterfacePackage() {
            return mapperInterfacePackage;
        }

        public void setMapperInterfacePackage(String mapperInterfacePackage) {
            this.mapperInterfacePackage = mapperInterfacePackage;
        }

        public String getModelProject() {
            return modelProject;
        }

        public void setModelProject(String modelProject) {
            this.modelProject = modelProject;
        }

        public String getDaoProject() {
            return daoProject;
        }

        public void setDaoProject(String daoProject) {
            this.daoProject = daoProject;
        }

        public String getGeneratorConfigTemplate() {
            return generatorConfigTemplate;
        }

        public void setGeneratorConfigTemplate(String generatorConfigTemplate) {
            this.generatorConfigTemplate = generatorConfigTemplate;
        }

        public String getiServiceTemplate() {
            return iServiceTemplate;
        }

        public void setiServiceTemplate(String iServiceTemplate) {
            this.iServiceTemplate = iServiceTemplate;
        }

        public String getServiceImplTemplate() {
            return serviceImplTemplate;
        }

        public void setServiceImplTemplate(String serviceImplTemplate) {
            this.serviceImplTemplate = serviceImplTemplate;
        }

        public String getControllerTemplate() {
            return controllerTemplate;
        }

        public void setControllerTemplate(String controllerTemplate) {
            this.controllerTemplate = controllerTemplate;
        }

        public boolean isGenerateService() {
            return generateService;
        }

        public void setGenerateService(boolean generateService) {
            this.generateService = generateService;
        }

        public boolean isGenerateController() {
            return generateController;
        }

        public void setGenerateController(boolean generateController) {
            this.generateController = generateController;
        }

        public static String[] getTemplateKeys() {
            return TEMPLATE_KEYS;
        }

        public String getServicePackage() {
            return servicePackage;
        }

        public void setServicePackage(String servicePackage) {
            this.servicePackage = servicePackage;
        }

        public String getServiceImplPackage() {
            return serviceImplPackage;
        }

        public void setServiceImplPackage(String serviceImplPackage) {
            this.serviceImplPackage = serviceImplPackage;
        }

        public String getControllerPackage() {
            return controllerPackage;
        }

        public void setControllerPackage(String controllerPackage) {
            this.controllerPackage = controllerPackage;
        }

        public String getServiceProject() {
            return serviceProject;
        }

        public void setServiceProject(String serviceProject) {
            this.serviceProject = serviceProject;
        }

        public String getServiceImplProject() {
            return serviceImplProject;
        }

        public void setServiceImplProject(String serviceImplProject) {
            this.serviceImplProject = serviceImplProject;
        }

        public String getControllerProject() {
            return controllerProject;
        }

        public void setControllerProject(String controllerProject) {
            this.controllerProject = controllerProject;
        }
    }

}
